﻿enum Direction
{
    Right = 0,
    Down = 1,
    Left = 2,
    Up = 3
}

static class DirectionExtensions
{
    public static void Move(this Direction direction, ref int x, ref int y)
    {
        var xOrY = (int)direction % 2;
        // xOrY: X(Down, Up): 1; Y(Right, Left): 0
        // 1 - xOrY: X(Down, Up): 0; Y(Right, Left): 1

        var sign = ((int)direction / 2) * (-2) + 1;
        // +(Right, Down): 1; -(Left, Up): -1

        x += xOrY * sign;
        y += (1 - xOrY) * sign;
    }

    public static Direction TurnRightHand(this Direction direction)
    {
        return (Direction)(((int)direction + 1) % 4);
    }
}

/// <summary>
/// I: https://leetcode-cn.com/problems/spiral-matrix/
/// II: https://leetcode-cn.com/problems/spiral-matrix-ii/
/// </summary>
public class Solution
{
    private IEnumerable<(Direction direction, int times)> GenerateSteps(int sizeX, int sizeY)
    {
        int tX = sizeX - 1;
        int tY = sizeY;

        Direction direction = Direction.Up;
        for (; ; )
        {
            direction = direction.TurnRightHand();
            if (tY is 0)
                yield break;
            yield return (direction, tY);
            tY--;

            direction = direction.TurnRightHand();
            if (tX is 0)
                yield break;
            yield return (direction, tX);
            tX--;
        }
    }

    #region I
    private IEnumerable<int> GetValues(int[][] matrix,
    IEnumerable<(Direction direction, int times)> steps)
    {
        int x = 0;
        int y = -1;
        foreach (var step in steps)
        {
            for (int i = 0; i < step.times; i++)
            {
                step.direction.Move(ref x, ref y);
                yield return matrix[x][y];
            }
        }
    }

    public IList<int> SpiralOrder(int[][] matrix)
    {
        var steps = this.GenerateSteps(matrix.Length, matrix[0].Length);
        return this.GetValues(matrix, steps).ToList();
    }
    #endregion

    #region II
    private void PutValues(int[][] matrix,
        IEnumerable<(Direction direction, int times)> steps)
    {
        int x = 0;
        int y = -1;
        int value = 1;
        foreach (var step in steps)
        {
            for (int i = 0; i < step.times; i++)
            {
                step.direction.Move(ref x, ref y);
                matrix[x][y] = value++;
            }
        }
    }

    public int[][] GenerateMatrix(int n)
    {
        var matrix = new int[n][];
        for (int i = 0; i < n; i++)
        {
            matrix[i] = new int[n];
        }
        var steps = this.GenerateSteps(matrix.Length, matrix[0].Length);
        this.PutValues(matrix, steps);
        return matrix;
    }
    #endregion
}